<script setup>
//================================================【组件通信】============================================================
//=======================================================【传入】
//父组件传递的数据
import {computed, onBeforeUnmount, reactive, ref, watch} from "vue";

const props = defineProps({
  //左侧组件标题
  leftComponentTitle:{
    type:String,
    required:true
  },
  //右侧组件标题
  rightComponentTitle:{
    type:String,
    required:true
  },
  //左侧、右侧搜索提示词
  searchPromptWords:{
    type:String,
    required:true
  },
  //树形，表格配置项
  configs: {
    type: Object,
    required: false,
    default: function() {
      let defaultConfigs = {};
      defaultConfigs.leftTreeShowAttribute = [ "name","title" ];
      defaultConfigs.leftTableColumns =
          [{
            title: '编码',
            key: 'dimItemCode',
            width: 200,
            align: 'left'
          },
            {
              title: '名称',
              key: 'dimItemName',
              width: 200,
              align: 'left'
            }];
      defaultConfigs.rightTableSearchKeys = [ "dimItemCode","dimItemName" ];
      defaultConfigs.rightTableColumns =
          [{
            title: '编码',
            key: 'dimItemCode',
            width: 200,
            align: 'left'
          },
            {
              title: '名称',
              key: 'dimItemName',
              width: 200,
              align: 'left'
            }];
      return defaultConfigs;
    }
  },
  //左侧树形初始数据
  leftTreeInitData:{
    type:Array,
    required:true
  },
  //右侧处理结果
  rightTableResultData:{
    type:Array,
    required:true
  }
});
//=======================================================【传出】
const emit = defineEmits(['leftTreeFuzzyMatch','update:rightTableResultData'])
//================================================【公用相关】============================================================
function leftSelectedItemReset() {
  leftSelectedItem.value.data = null
  leftSelectedItem.value.isLeaf = false
}
//清除左侧选中项状态
function selectorDataAndStatusReset() {
  //清除左侧搜索词，并触发左侧表格重置
  leftSearchWord.value = ''
  //清除右侧搜索词，并触发右侧表格重置
  rightSearchWord.value = ''
  //重置左侧选择项
  leftSelectedItemReset()
  //重置右侧选择项
  rightSelectedItem.value = null
}
//左侧选中项
const leftSelectedItem = ref({data:null,isLeaf:false})
//右侧选中项
const rightSelectedItem = ref(null)
//将树形数据转换为表格数据
function transDataTreeToTable(treeDataObj) {
  let tableDataArr = [];
  // 如果是单个对象，将其包装成数组
  const iterableData = Array.isArray(treeDataObj) ? treeDataObj : [treeDataObj];
  for (const tempArrElement of iterableData) {
    //转化对象
    const tempObject = {
      [leftTableColumns.value[0].key]: tempArrElement[props.configs['leftTreeShowAttribute'][0]],
      [leftTableColumns.value[1].key]: tempArrElement[props.configs['leftTreeShowAttribute'][1]]
    }
    tableDataArr.push(tempObject)
  }
  return tableDataArr
}
//================================================【组件属性】============================================================
//中间操作按钮状态控制
const selectDisable = computed(()=> !leftSelectedItem.value.data)
const selectAllDisable = computed(()=> leftTreeData.length === 0 && leftTableData.length === 0)
const selectSubDisable = computed(()=> leftSearchWord.value !==''||!leftSelectedItem.value.data || leftSelectedItem.value.isLeaf)
const selectAllSubDisable = computed(()=> leftSearchWord.value !==''||!leftSelectedItem.value.data || leftSelectedItem.value.isLeaf)
const removeDisable = computed(()=> !rightSelectedItem.value)
const removeAllDisable = computed(()=> rightTableData.length === 0)
//==================================================监听左侧初始化数据
watch(()=>props.leftTreeInitData,(newValue)=>{
    leftTreeData.splice(0,leftTreeData.length,...newValue)

  //如果左侧树形初始数据为空，则当前组件所有手动操作过的数据需要重置
  if (newValue.length === 0)
    selectorDataAndStatusReset();
},{deep:true})
//==================================================左侧搜索
//搜索词
const leftSearchWord = ref('')
//搜索词监听
watch(leftSearchWord,(newValue,oldValue)=>{
  if (oldValue === '' && newValue !== ''){
    //原树形有选中节点，搜索切换至搜索结果表格后，需要将原节点设置为未选中状态
    let selectedNodes = leftTreeElement.value.getSelectedNodes()
    if (selectedNodes.length > 0) {
      selectedNodes[0].selected = false
      //重置左侧选择项
      leftSelectedItemReset();
    }
  }

  if (newValue !== ''){
    emit('leftTreeFuzzyMatch',newValue,handleSearchResult)
  }else {
    //返回原树形，搜索表格清空
    leftTableData.splice(0,leftTableData.length)
    //重置左侧选择项
    leftSelectedItemReset();
  }
})
//父组件异步请求成功后，获取返回结果
function handleSearchResult(matchedResult) {
  leftTableData.splice(0,leftTableData.length,...transDataTreeToTable(matchedResult))
}
//==================================================右侧搜索
//搜索词
const rightSearchWord = ref('')
//搜索前原数据副本
const rightSearchFB = reactive([])
//右侧表格搜索
function rightTableFuzzyMatch(searchWord) {
  const temp = rightSearchFB.filter(preFilterItem => {
    return preFilterItem[props.configs['rightTableSearchKeys'][1]].indexOf(searchWord) !== -1
        ||
        preFilterItem[props.configs['rightTableSearchKeys'][0]].indexOf(searchWord.trim().toLowerCase()) !== -1
        ||
        preFilterItem[props.configs['rightTableSearchKeys'][0]].indexOf(searchWord.trim().toUpperCase()) !== -1
  })
  rightTableData.splice(0, rightTableData.length, ...JSON.parse(JSON.stringify(temp)))
}

//搜索词监听
watch(rightSearchWord,(newValue,oldValue)=>{
  //初次搜索，原表格有选中行，搜索切换至搜索表格后，需要将原选中行设置为不选中状态，对应按钮状态需要对应更新
  if (newValue !== ''
      &&
      oldValue === ''
      &&
      rightSelectedItem.value
      &&
      rightSelectedItem.value.highlight){
    rightTableData
        .filter(preFilterItem => {
          return preFilterItem[rightTableColumns.value[0].key] === rightSelectedItem.value[rightTableColumns.value[0].key]
        })
        .map(preMapItem => preMapItem.highlight = false)
    rightSelectedItem.value = null
  }
  //初次搜索
  if (newValue !== '' && oldValue === '')
    rightSearchFB.splice(0, rightSearchFB.length, ...rightTableData)


  rightTableFuzzyMatch(newValue);

  //返回原表格
  if (newValue === '') {
    rightSelectedItem.value = null
    rightTableData.splice(0, rightTableData.length, ...rightSearchFB)
    rightSearchFB.splice(0,rightSearchFB.length)
  }
})
//表格数据
const rightTableData = reactive([])
//监听右侧表格数据
watch(rightTableData,(newValue)=>{
  //当右侧表格属于搜索状态时，向父组件传递的数据应该是副本数据
  if(rightSearchWord.value === '')
      emit('update:rightTableResultData', newValue)
  else {
    //临界状态：搜索前，点击某一行，搜索时需要将这一行的高亮取消，取消操作触发了本监听
    //再取消后，还没给副本赋值时，不能将空的副本传递给父组件，否则会因为动态绑定，影响本组件的
    if(rightSearchFB.length > 0)
      emit('update:rightTableResultData', rightSearchFB)
  }
})
//==================================================监听右侧结果数据
watch(props.rightTableResultData,(newValue)=>{
  rightTableData.splice(0,rightTableData.length,...JSON.parse(JSON.stringify(newValue)))
})
//================================================【普通方法】============================================================
//=====================================================================================【左侧】
//左侧数据  选择至  右侧表格
function doSelect(leftSelectedObj) {
  // 如果是单个对象，将其包装成数组
  const leftSelectedArr = Array.isArray(leftSelectedObj) ? leftSelectedObj : [leftSelectedObj];
  //排重
  for (const leftSelectedArrElement of leftSelectedArr){
    if (rightSearchWord.value === ''
        &&
        rightTableData.findIndex(preFindItem => {
          return preFindItem[rightTableColumns.value[0].key]
              ===
              leftSelectedArrElement[leftTableColumns.value[0].key]})
        === -1)
      rightTableData.push(leftSelectedArrElement)


    if (rightSearchWord.value !== ''
        &&
        rightSearchFB.findIndex(preFindItem => {
          return preFindItem[rightTableColumns.value[0].key]
              ===
              leftSelectedArrElement[leftTableColumns.value[0].key]})
        === -1)
      rightSearchFB.push(leftSelectedArrElement) && rightTableFuzzyMatch(rightSearchWord.value)
  }
}
//==================================================左侧树形
//树形元素
const leftTreeElement = ref(null)
//树形数据
const leftTreeData = reactive([])
//树形单击
function leftTreeSelect(nodeElement) {
  // 当前没有选中树形节点时
  if (leftTreeElement.value.getSelectedNodes().length === 0) {
    leftSelectedItem.value.data = null
    leftSelectedItem.value.isLeaf = false
  }else {
    const isLeaf = nodeElement.children === null
    leftSelectedItem.value.data = transDataTreeToTable(nodeElement)[0]
    leftSelectedItem.value.isLeaf = isLeaf
  }
}
//树形双击
function leftTreeDBClick(nodeElement) {
  doSelect(transDataTreeToTable(nodeElement)[0])
}
//获取树形下级节点
function getAllChildrenNode(node) {
  let children = node.children;
  if (children && children.length > 0) {
    let allChildren = [];
    for (let i = 0; i < children.length; i++) {
      let child = children[i];
      allChildren.push(child);
      let subChildren = getAllChildrenNode(child);
      if (subChildren && subChildren.length > 0) {
        allChildren = allChildren.concat(subChildren);
      }
    }
    return allChildren;
  } else {
    return null;
  }
}
//获取树形节点属性值
function getTreeNodeAttrValue(data, attributeName) {
    return data[attributeName]
}
//==================================================左侧表格
//表格字段
const leftTableColumns = computed(() => {
  return props.configs['leftTableColumns'];
})
//表格数据
const leftTableData = reactive([])
//表格单击
function leftTableClick(rowData) {
  //表格只允许同一时间显示一条高亮行
  leftTableData
      .filter(preFilterItem => {
        return preFilterItem[leftTableColumns.value[0].key] !== rowData[leftTableColumns.value[0].key]
      })
      .map(preMapItem => preMapItem.highlight = false)
  rowData.highlight?
      leftSelectedItem.value.data = JSON.parse(JSON.stringify(rowData))
      :
      leftSelectedItem.value.data = null
}
//表格双击
function leftTableDBClick(rowData) {
  doSelect(JSON.parse(JSON.stringify(rowData)))
}
//=====================================================================================【中间】
//==================================================选择
//单选
function select() {
  //左侧搜索表格选中一行(选中时高亮),点击选择。选择至右侧应该取消高亮
  leftSelectedItem.value.data.highlight = false
  doSelect(leftSelectedItem.value.data)
}
//==================================================全选
//全选
function selectAll() {
  //左侧表格数据  --->   右侧表格数据
  if (leftSearchWord.value !== '') {
    doSelect(JSON.parse(JSON.stringify(leftTableData)))
  }else {
    //左侧树形数据  --->   右侧表格数据
    for (let leftTreeDatum of leftTreeData) {
      let toAddArr = []
      //添加根节点
      toAddArr.push(...transDataTreeToTable(leftTreeDatum))
      if (leftTreeDatum.children && leftTreeDatum.children.length > 0){
        //添加除根节点外的所有下级节点
        const allChildrenNode = getAllChildrenNode(leftTreeDatum)
        toAddArr.push(...transDataTreeToTable(allChildrenNode))
      }
      //执行添加
      doSelect(toAddArr)
    }
  }
}
//==================================================直接下级
//选择直接下级
function selectSub() {
  doSelect(transDataTreeToTable(leftTreeElement.value.getSelectedNodes()[0].children))
}
//==================================================所有下级
//选择全部下级
function selectAllSub() {
  doSelect(transDataTreeToTable(getAllChildrenNode(leftTreeElement.value.getSelectedNodes()[0])))
}
//==================================================删除
//删除一项
function remove() {
  doRemove(rightSelectedItem.value)
}
//==================================================全删
//删除全部
function removeAll() {
  doRemove(rightTableData)
}
//=====================================================================================【右侧】
//右侧数据  删除
function doRemove(rightSelectedObj) {
  // 如果是单个对象，将其包装成数组
  const rightSelectedArr = Array.isArray(rightSelectedObj) ?
                                JSON.parse(JSON.stringify(rightSelectedObj))
                                :
                                JSON.parse(JSON.stringify( [rightSelectedObj]))

  if (rightSearchWord.value !== '')
    for (let rightTableDatum of rightSelectedArr) {
      rightSearchFB.splice(findRightTableIndex(rightSearchFB, rightTableDatum), 1)
    }

  for (let rightTableDatum of rightSelectedArr) {
    rightTableData.splice(findRightTableIndex(rightTableData,rightTableDatum),1)
  }
  rightSelectedItem.value = null
}
//==================================================右侧表格
//表格字段
const rightTableColumns = computed(() => {
  return props.configs['rightTableColumns'];
})
//表格单击
function rightTableClick(rowData) {
  rightTableData
      .filter(preFilterItem => {
        return preFilterItem[rightTableColumns.value[0].key] !== rowData[rightTableColumns.value[0].key]
      })
      .map(preMapItem => preMapItem.highlight = false)
  rowData.highlight?
      rightSelectedItem.value = rowData
      :
      rightSelectedItem.value = null
}
//表格双击
function rightTableDBClick(rowData) {
  doRemove(rowData)
}
//表格查询下标
function findRightTableIndex(tableData,targetItem) {
  return tableData.findIndex(preFindItem => {
    return preFindItem[rightTableColumns.value[0].key]
        ===
        targetItem[rightTableColumns.value[0].key]
  });
}
//================================================【生命周期】============================================================
onBeforeUnmount(()=>{
  selectorDataAndStatusReset()
})
</script>

<template>
  <div class="selectContent">
    <div class="leftComponent">
      <div>{{leftComponentTitle}}</div>
      <div class="leftSearch">
        <j-input long
                 icon="icon-search"
                 :placeholder="searchPromptWords"
                 :disabled="leftSearchWord === '' && leftTreeData.length === 0"
                 v-model="leftSearchWord">
        </j-input>
      </div>
      <div v-if="leftSearchWord === ''" class="leftTree">
        <j-tree ref="leftTreeElement"
                select-type="check"
                v-model:data="leftTreeData"
                @select="leftTreeSelect"
                @dblclick-node="leftTreeDBClick">
          <template #node=" nodeData ">
              <span>
                  {{ getTreeNodeAttrValue(nodeData, configs['leftTreeShowAttribute'][0]) }}
                    |
                  {{ getTreeNodeAttrValue(nodeData, configs['leftTreeShowAttribute'][1]) }}
              </span>
          </template>
        </j-tree>
      </div>
      <div v-else class="leftTable">
        <j-table ref="leftTableElement"
                 highlight
                 height="100%"
                 :columns="leftTableColumns"
                 :data="leftTableData"
                 @row-click="leftTableClick"
                 @row-dblclick="leftTableDBClick">
        </j-table>
      </div>
    </div>
    <div class="middleButtons">
      <j-button class="middleButton" @click="select" :disabled="selectDisable">选择</j-button>
      <j-button class="middleButton" @click="selectAll" :disabled="selectAllDisable">全选</j-button>
      <j-button class="middleButton" @click="selectSub" :disabled="selectSubDisable">直接下级</j-button>
      <j-button class="middleButton" @click="selectAllSub" :disabled="selectAllSubDisable">所有下级</j-button>
      <j-button class="middleButton" @click="remove" :disabled="removeDisable">删除</j-button>
      <j-button class="middleButton" @click="removeAll" :disabled="removeAllDisable">全删</j-button>
    </div>
    <div class="rightComponent">
      <div>{{ rightComponentTitle }}</div>
      <div class="rightSearch">
        <j-input long
                 icon="icon-search"
                 :placeholder="searchPromptWords"
                 :disabled="rightSearchWord === '' && rightTableData.length === 0"
                 v-model="rightSearchWord">
        </j-input>
      </div>
      <div class="rightTable">
        <j-table ref="rightTableElement"
                 highlight
                 height="100%"
                 :columns="rightTableColumns"
                 :data="rightTableData"
                 @row-click="rightTableClick"
                 @row-dblclick="rightTableDBClick">
        </j-table>
      </div>
    </div>
  </div>
</template>

<style scoped>
.selectContent{
  display: flex;
  flex-direction: row;
  justify-content: center;
  width: 912px;
  height: 530px;
}
.leftComponent{
  display: flex;
  flex-direction: column;
}
.leftTree{
  width: 400px;
  height: 475px;
  border: 1px #f1f1f1 solid;
  overflow-x: auto;
}
.leftTable {
  width: 400px;
  height: 475px;
  overflow-y: auto;
  border: 1px #f1f1f1 solid;
}
.middleButtons{
  width: 175px;
  height: 475px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 22px;
  align-items: center;
}
.middleButton{
  width: 90px;
  height: 26px;
}
.rightComponent{
  display: flex;
  flex-direction: column;
}
.rightTable{
  width: 400px;
  height: 475px;
  overflow-y: auto;
  border: 1px #f1f1f1 solid;
}
</style>
